<?php

/**
 * Class Util
 *
 * @package Agi\Util
 * @author  Janpoem created at 2015/3/11 8:08
 * @see http://git.oschina.net/janpoem/Agi-PHP-Util/blob/master/src/Agi/Util/Util.php
 */
class Util
{

    // php值类型的字面值
    /** 字符串类型 */
    const PHP_STR = 'string';
    /** 数组类型 */
    const PHP_ARY = 'array';
    /** 对象类型 */
    const PHP_OBJ = 'object';
    /** 资源类型 */
    const PHP_RES = 'resource';
    /** 整型类型 */
    const PHP_INT = 'integer';
    /** 浮点 */
    const PHP_FLOAT = 'double';
    /** 布尔类型 */
    const PHP_BOOL = 'boolean';
    /** null类型 */
    const PHP_NULL = 'NULL';

    const PATH_NOISE = '\\/.';

    /**
     * Pear即PHP 5.3以前的类命名空间的分隔符
     * _表示的是一个目录分隔符，My_Namespace_Class => My/Namespace/Class
     */
    const SPR_NS_PEAR = '_';

    /** PHP 5.3以后的类命名空间的分隔符 */
    const SPR_NS_PHP53 = '\\';

    /** 深入查询函数的分隔符 */
    const SPR_DEPTH_SEARCH = '->';

    /** Http的目录分隔符 */
    const SPR_HTTP_DIR = '/';

    /**
     * 深入查询$data
     *
     * <code>
     * $data = array(
     *     'level1' => array(
     *         'level2' => array(
     *             0, 1, 2, 3, 4, 5
     *         );
     *     ),
     * );
     *
     * depthQuery($data, 'level1->level2'); // return array(0,1,2,3,4,5)
     * depthQuery($data, 'level1->level2->0'); // return 0
     * depthQuery($data, 'not_exist', 'test'); // return 'test'
     *
     * </code>
     *
     * $keys可以为数组格式，如：array('level1', 'level2');
     *
     * @param array|object $data    数据源
     * @param string|array $keys    查询的keys，字符串格式为：`'key1->key2->0'`，数组格式：`array('key1', 'key2', 0)`
     * @param mixed        $default 默认值，当查询的keys的值不存在时，返回该默认值。
     *
     * @return mixed
     */
    public static function depthQuery($data, $keys, $default = null)
    {
        if (empty($keys))
            return $data;
        $keysType = gettype($keys);
        if ($keysType === self::PHP_STR) {
            if (strpos($keys, self::SPR_DEPTH_SEARCH) !== false) {
                $keys = explode(self::SPR_DEPTH_SEARCH, $keys);
                $keysType = self::PHP_ARY;
            } else {
                // Janpoem 2014.09.21
                // 调整了一些，原来只是检查isset，现在增加empty的判断
                // 需要做更长时间的监控，是否有副作用
                if (is_array($data))
                    return !isset($data[$keys]) || ($data[$keys] != 0 && empty($data[$keys])) ? $default : $data[$keys];

                elseif (is_object($data))
                    return !isset($data->{$keys}) || ($data[$keys] != 0 && empty($data->{$keys})) ? $default : $data->{$keys};
                else
                    return $default;
            }
        }
        if (!empty($data) || (!empty($keys) && $keysType === self::PHP_ARY)) {
            foreach ($keys as $key) {
                if (!is_numeric($key) && empty($key)) continue;
                // 每次循环，一旦没有$key，就退出
                // 不是array的話也到頭了
                // Janpoem 2014.09.21
                // 调整了一些，原来只是检查isset，现在增加empty的判断
                // 需要做更长时间的监控，是否有副作用
                if (is_array($data)) {
                    if (!isset($data[$key]) || ($data[$key] != 0 && empty($data[$key])))
                        return $default;
                    else
                        $data = $data[$key];
                } elseif (is_object($data)) {
                    if (!isset($data->{$key}) || ($data->{$key} != 0 && empty($data->{$key})))
                        return $default;
                    else
                        $data = $data->{$key};
                }
            }
            return $data;
        } else
            return $default;
    }

    /**
     * 合并数组
     *
     * @param array $source        数据源
     * @param array $data          合并的数据
     * @param bool  $isIgnoreEmpty 是否忽略空值，忽略空值的话，任意empty的值都不会合并到数据源中。
     *
     * @return array
     */
    public static function mergeArray(array $source, array $data, $isIgnoreEmpty = true)
    {
        if (empty($data))
            return $source;
        foreach ($data as $key => $value) {
            if (is_array($value)) {
                if (!isset($source[$key]) || !is_array($source[$key]))
                    $source[$key] = $value;
                else if (!empty($value))
                    $source[$key] = self::mergeArray($source[$key], $value, $isIgnoreEmpty);
            } else {
                if (empty($value) && $isIgnoreEmpty)
                    continue;
                $source[$key] = $value;
            }
        }
        return $source;
    }

    /**
     * 生成一个url
     *
     * @todo 这个方法需要额外独立
     *
     * @param string            $path    路径 或 一个URL
     * @param string|array|null $query   要替换的 Query String 数据，支持字符串或数组两种格式。
     * @param string            $baseUrl url前缀，如果 $path 是一个完整的URL，则会忽略这个 $baseUrl，
     *                                   只在$path是一个路径时，该值才会生效。
     *
     * @return string 返回一条URL
     */
    public static function mkUrl($path = null, $query = null, $baseUrl = null)
    {
        if (empty($baseUrl)) {
            // 预定一个前置全局常量，允许替换掉baseUrl
            $baseUrl = defined('HTTP_BASE') ? HTTP_BASE : self::SPR_HTTP_DIR;
        }
        if (!empty($baseUrl) && $baseUrl !== self::SPR_HTTP_DIR) {
            $baseUrl = rtrim($baseUrl, self::PATH_NOISE);
            if ($baseUrl[0] !== self::SPR_HTTP_DIR)
                $baseUrl = self::SPR_HTTP_DIR . $baseUrl;
        }

        // 初始化传入路径
        if (empty($path))
            $path = self::SPR_HTTP_DIR;
        else {
            $path = ltrim($path, self::PATH_NOISE);
            if (empty($path))
                $path = self::SPR_HTTP_DIR;
        }

        // 解析传入的路径，重新拼接出基本的路径
        $pathHasQuery = false;
        $pathQuery = null;
        $parse = array();
        if ($path !== self::SPR_HTTP_DIR) {
            $parse = parse_url($path);
            if (!empty($parse['query'])) {
                parse_str($parse['query'], $pathQuery);
                $pathHasQuery = true;
            }
            if (empty($parse['host'])) {
                $path = $parse['path'];
                if ($path[0] !== '.') {
                    if ($path[0] !== self::SPR_HTTP_DIR)
                        $path = self::SPR_HTTP_DIR . $path;
                    if ($baseUrl !== self::SPR_HTTP_DIR && stripos($path, $baseUrl) !== 0)
                        $path = $baseUrl . $path;
                }
                // 前置一个HTTP_DIR的全局常量
                if (defined('HTTP_DIR') && $path === HTTP_DIR)
                    $path .= self::SPR_HTTP_DIR;
            } else {
                $path = "{$parse['scheme']}://{$parse['host']}{$parse['path']}";
            }
            $path = rtrim($path, self::PATH_NOISE);
        } else {
            if ($baseUrl !== self::SPR_HTTP_DIR)
                $path = $baseUrl;
        }

        if (empty($path))
            $path = self::SPR_HTTP_DIR;

        // 拼接query
        if (!empty($query)) {
            $type = gettype($query);
            if ($type === self::PHP_STR) {
                parse_str($query, $query);
                $type = self::PHP_ARY;
            } else if ($type === self::PHP_OBJ) {
                $query = get_object_vars($query);
                $type = self::PHP_ARY;
            }
            if ($type === self::PHP_ARY && !empty($query)) {
                if (!empty($pathQuery))
                    $query = self::mergeArray($pathQuery, $query, false);
                $qs = http_build_query($query);
                if (!empty($qs))
                    $path .= '?' . $qs;
            }
        } else {
            if (!empty($pathQuery))
                $qs = http_build_query($pathQuery);
            if (!empty($qs))
                $path .= '?' . $qs;
        }

        // 拼接fragment
        if (!empty($parse['fragment']))
            $path .= '#' . $parse['fragment'];
        return $path;
    }

    /**
     * 值内容是否相等
     *
     * 动态类型语言的值类型检查真的蛋疼
     *
     * @param mixed $old
     * @param mixed $new
     *
     * @return bool 是否相等
     */
    public static function eq($old, $new)
    {
        if ($old === $new)
            return true;
        $oldType = gettype($old);
        $newType = gettype($new);
        if ($oldType !== self::PHP_ARY && $oldType !== self::PHP_OBJ && $oldType !== self::PHP_RES &&
            $newType !== self::PHP_ARY && $newType !== self::PHP_OBJ && $newType !== self::PHP_RES
        ) {
            if ($old === true) $old = 1;
            elseif ($old === false) $old = 0;
            if ($new === true) $new = 1;
            elseif ($new === false) $new = 0;
            return strval($old) === strval($new);
        } else {
            return $old === $new;
        }
    }

}
